分享 | CAL PB级的日志存储迁移到Ceph的实践
供稿 | Unified Monitoring Platform
翻译&编辑 | 顾欣怡
本文3663字,预计阅读时间11分钟
更多干货请关注“eBay技术荟”公众号
导读
CAL(Centralized Application Logging)是eBay的集中式应用日志框架,每日处理大量数据。为满足日趋增大的存储要求,eBay开始将CAL上PB级的日志存储迁移到Ceph。本文主要讲述如何修改现有监控系统设计以符合该项目的要求,以及在项目实施中遇到了哪些具体问题。
一、背景
CAL(Central Application Logging) 系统主要负责收集和处理eBay内部各个应用池的日志,日处理超过3PB的数据,提供包含原始日志、聚合报告、正则表达式搜索等在内的多种形式的结果供SRE(Site Reliability Engineer)和PD们(Product Developer,产品开发设计师)日常监控使用。从21世纪初上线至今,已经有10多年的历史了。
早期后台存储日志使用的是NetApp公司提供的Filer,作为NFS(Network File System,网络文件系统)的解决方案,所有CAL相关的模块都需要从Filer上读写数据。
CAL使用的Filer,是由图1所示的多个小的物理卷组成的,它有2个主要的问题:
1. 一旦需要增加一个新的卷,或者移除一个老的卷,都需要所有CAL组件做一次代码发布,效率很低。
2. 需要在写日志的时候选择最空闲的卷,来保证每个小卷都尽可能地使用均匀,但没有哪个算法能保证绝对均匀,空间使用效率不是很好。
图1
另一方面,随着日志数据量的逐年增长,CAL Filer的容量(Volume)也增大到每个数据中心大约300多T,但日志的有效时间却从原先的几天下降到现在的20个小时。同时,鉴于NetApp Filer提供的支持和监控都很有限,已经越来越不能满足eBay CAL团队对于存储的要求。再者,CAL团队也在寻求更快更稳定更便宜的NFS存储方案。
鉴于以上几点原因,加上eBay内部也有团队深耕新式存储Ceph多年,很自然地,CAL团队着手和Ceph团队一起开始了CAL-on-Ceph这样一个跨团队的合作项目。
二、系统设计的修改
对于这样一个大规模的CAL系统,考虑到对Filer的使用经验和现状以及实时迁移,我们对现有的监控系统设计做了以下几个方面的修改。
1. 目录结构简化
在CAL系统设计之初,出于易用性考虑,加之当时日志的容量并不是很大,我们设计了一种特殊的文件存放的目录结构,如图2所示,深度15+。这个目录结构包含了原始日志文件的元数据信息(环境,日期,应用池,客户端IP等)。因此几类使用广泛的API,例如getPools(指定日期和环境,获取所有符合条件的应用池)和getMachines (指定日期,环境,应用池,获取所有符合条件的机器(machine))都能通过只遍历目录来得到结果,不需要任何其他的依赖。
图2(点击可观看大图)
可惜随着日志容量的与日俱增,这种把文件系统当数据库来使用的用例,已经让文件系统苦不堪言。随之带来的影响就是,文件系统越来越频繁地出现读写文件十分缓慢的现象,极大地影响了用户体验。即使把Filer换成Ceph,这个问题也亟待解决。
考虑到Ceph后端MDS(Metadata Server,元数据服务器)集群的可扩展性,我们将目录结构简化成下面的格式,如图3所示,深度为5(其中第三层为0-1023的数字,主要是为了方便分散文件到MDS集群的不同服务器上去);将大量原本占据目录名存放的信息,转移到了文件名上。
图3(点击可观看大图)
2. 基于数据库的日志元数据存储和查询
目录结构简化只是解决了文件系统的问题,上面提到的很多API还是最适宜由数据库来服务,一条select语句就能搞定。因此,我们重新梳理了所有使用到文件目录的API,将最重要的四个元组(日期,环境,应用池,客户端IP)提取出来,作为创建数据库表的基本元素;并设计了一个包含24张表(每小时一张),且每张表包含四个列的数据库。考虑到eBay DBA的可用支持,最终选择了MySQL。
鉴于eBay拥有超过百万台的服务器,每个服务器又通过多个连接同时将日志写进CAL系统,可以预见这张表规模不会小。如果日志进入CAL系统后,上述API能在很短的SLA内被用户所消费,数据库的最大TPS可能会达到50k(主要是写的TPS)。如果不采取优化,只是一条一条写的话,性能上会有问题。
我们调研了几种方案,最终选用了“LOAD DATA LOCAL INFILE”的方式来做批量写。实验表明,这种将客户端多个插入请求合并到本地文件,再传输到MySQL server端去做批量插入的方法,最大能支持322k TPS,足以满足当前以及未来很长一段时间内的需求。
3. 文件IO操作异步化
在迁移的过程中,为了验证Ceph的性能,需要先做双写。在不影响当前数据流到Filer的前提下,克隆数据,导入Ceph。在预发布(Staging)环境做测试的时候,就已经发现Ceph相比于Filer,一个劣势就是会出现一定频率的“卡死”现象,即某些CAL 服务器会突然连不上Ceph,所有对于Ceph的文件操作都直接没有响应了。同时还需要考虑到Ceph可能会有宕机等其他不可用的情况,在这些情况下,都不能影响到Filer的数据流。
CAL原先是同步处理一条一条的日志,一旦碰到这种现象将会直接影响本该正常写到Filer的日志。现在我们将所有文件IO类的操作都从主处理流程上解耦,放到后台线程池中进行处理,辅之以线程超时以避免“卡死”的情况。
同时,考虑到快速失败机制(fast fail),我们在后台有个额外线程,每隔一段时间主动检查Ceph的可用性。如果不可用,涉及到Ceph的文件IO操作会直接跳过,进一步减少不必要的内存堆积。
三、项目实施中碰到的问题
尽管事先已经考虑了诸多可能会出现的问题,并在预发布(Staging)环境进行了一定规模的测试,但是在生产(Production)环境实施的时候,我们仍然碰到了几个问题。下面分享几个主要的有代表性的问题。
1. MySQL查询性能急剧下降
首次在生产环境上线大约半小时后,我们就观测到几个主要API访问MySQL时出现大规模的读取困难,原先的查询秒级返回,变成现在的需要超过1分钟才能返回。查看MySQL的监控后,发现负载急剧增长,于是不得不先回滚。如图4所示:
图4
通过排查日志,并且和DBA一起研究过后,我们将可能的原因缩小到以下几方面,并做了相应的优化:
现有的DB 数据保留(retention)方法是采用 “DELETE * from TABLE xx where date < ${date}”,定期删除某个时间点之前的所有条目。但这并不是MySQL友好的方式,在目前单表几百万数据的情况下,随着时间的推移,会产生大量的数据碎片,进而造成MySQL主、从节点之间同步困难,最终影响数据库的性能。
咨询了DBA后,考虑到记录有效使用时间,我们将数据保留方法调整为若干个小时(<24小时)后,直接“truncate”掉整张表,既快又不会产生碎片。
现有的写数据库操作逻辑是:当客户端同CAL 服务器建立连接后,会将一条记录放在CAL 服务器的本地缓存里,然后周期性地将缓存里的所有记录都一次性通过上面提到的”LOAD DATA LOCAL INFILE”的方式写到MySQL里。但每个客户端会建立若干个(一般为5个)连接到CAL 服务器。因此,可能来自同一个客户端的多个连接会落到同一个CAL 服务器里。
考虑到这种情况,在略微降低SLA的前提下,将周期性刷新缓存到MySQL的时间间隔从原来的一分钟,增大到了五分钟。同时在刷新前,再对缓存做一次去重。最终将单表的条目数量从原先的最多900多万条,减少到现在的500万条左右。
由于设计之初并没有考虑做表的索引,通过上面发生的问题,我们开始思考从索引的角度来进一步优化表的查询。考虑到现在的4列 (日期,应用池,环境,客户端IP),其中日期的基数(cardinality)不高,因为一张表里存放的都是某一个小时的记录;环境变量也只有prod,sandbox等几个,显然大部分记录都集中在prod里,因此也不适合索引;客户端IP就更别说了,基数最高,索引没什么意义。
因此只有应用池这一列了,其索引基数大致几千,上百万条记录分布在这几千个应用池里,比较适合做索引。在对应用池这一列做了索引后,50个线程同时使用getMachines API,其平均响应时间从原先的24秒下降到现在的1秒左右。
以上3个方面的优化都完成后,在生产环境实施该项目时就再也没出过MySQL方面的问题了。
2.频繁full GC
在生产环境做双写的时候,我们发现了一个现象:某些CAL进程运行了大概30分钟后,整个进程就没有任何响应了,也不写日志了。翻阅GC(Garbage Collection, 垃圾回收)日志,发现它在运行20分钟后,就开始频繁做full GC,不久之后每次GC都释放不掉任何内存了,总是10G->10G。
经过数次失败后,终于在进程开始full GC时,成功地完成了一次heapdump。通过MAT(Memory Analyzer Tool)工具分析,发现里面有超过24K个ScheduledFutureTask对象(如图5所示),每个持有了大概400K堆内存,总计保留了将近10G的堆内存。
这个ScheduledFutureTask里包含了一个rotateFuture(保留了一些堆外内存的引用),主要是负责定期地将缓存里保留的数据,刷到后端的Filer或者Ceph里。在CAL认为Ceph不可用的情况下,这部分rotateFuture没有能够进入正常的释放流程,导致越堆越多,最终用尽了10G堆内存的限制。
图5
发现了问题后解决方案并不复杂,添加在Ceph不可用情况下的rotateFuture释放即可,之后的模拟测试和最终上线后便再也没碰到过这个问题了。
3.存储端同时读写同一文件时性能急剧下降
当只往Ceph写,但是不从Ceph里读数据的时候,Ceph端显示的Write IOPS(每秒进行读写操作的次数)只有大约40K左右。但是,当从Ceph端开始大规模读数据的时候,其Write IOPS突然飙升到了170K左右,然后整个集群的延迟开始显著增加。
对于CAL端来说,我们观测到的现象是:刷数据到Ceph的速度明显下降,经常出现写的速度跟不上从客户端读数据的速度,导致本地缓冲数据的队列迅速堆满,进而开始丢数据。
和Ceph团队一起研究过后,发现Ceph为了性能考虑,在真正刷数据到持久化层之前,做了一层缓存。Ceph会根据一定逻辑将收到的若干条数据做批量写。但是,这有个前提,即缓存里的数据没有别人需要消费。当CAL从Ceph端读取数据之后,每个文件都被多个客户端同时打开,同时进行读和写的操作,此时Ceph所做的缓存就失效了,IOPS也就回归到了原本的真实水平。
针对这一现状,CAL 团队的应对方法是自己实现批量写,来减少刷数据的次数。原来是将一个个包含多条日志的数据块直接压缩,然后写到后端存储去。现在是将多个数据块压缩好后,合并成一个大的数据块,再写到后端存储去,相比之前,现在的方法进一步减少了刷数据的次数,降低了Write IOPS。Ceph端的监控显示:Write IOPS从170k降到了30k左右,整体集群工作状态良好。
四、总结
虽然初衷只是替换掉日处理超过3PB数据的CAL系统所使用的后端存储,但是我们没有满足于此。通过一系列的设计优化,不仅成功地将数据迁移到了Ceph平台上,更借此机会优化了CAL系统的日志存储结构,获得了更好的读写性能,还降低了成本。
截止2019年5月底,eBay已将一整个数据中心的CAL日志搬迁到了Ceph上,相比以前的Filer,共节省了大约50万美金,未来随着更多数据中心的日志迁移,相信还会节省更多。
↓点击阅读原文,eBay大量优质职位虚席以待。